package com.algorithms.absdatatypes;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.algorithms.absdatatypes.Apply.GRAPH_DIRECTION;
import com.algorithms.absdatatypes.Apply.GRAPH_EDGE_DIRECTION;
import com.algorithms.datatypes.GraphNodeImpl;

public abstract class AbstractGraph<K extends Comparable, T, NM extends Number> implements
		Graph<K, T, NM>
{

	LinkedHashMap<GraphNode<K, T>, LinkedList<GraphEntry<GraphNode<K, T>, NM>>> adjacencyList = null;

	Apply.GRAPH_DIRECTION graphDirection;

	
	public AbstractGraph(Apply.GRAPH_DIRECTION input)
	{
		adjacencyList = new LinkedHashMap<GraphNode<K, T>, LinkedList<GraphEntry<GraphNode<K, T>, NM>>>();
		graphDirection = input;
	}

	
	public int getSize()
	{
		return 0;
	}

	
	public Set<GraphNode<K, T>> getVertices()
	{
		return adjacencyList.keySet();
	}

	
	public Map<GraphNode<K, T>, GraphNode<K, T>> getEdges()
	{
		Map<GraphNode<K, T>, GraphNode<K, T>> map = new LinkedHashMap<GraphNode<K, T>, GraphNode<K, T>>();
		for (GraphNode<K, T> vertex1 : adjacencyList.keySet())
		{
			for (GraphEntry<GraphNode<K, T>, NM> vertex1ConnectedTo : adjacencyList
					.get(vertex1))
			{
				map.put(vertex1, vertex1ConnectedTo.getKey());
			}
		}
		return map;
	}

	
	public void addVertex(GraphNode<K, T> vertex1)
	{
		if (!adjacencyList.containsKey(vertex1))
		{
			adjacencyList.put(vertex1, null);
		}

	}

	
	public void removeEdge(GraphNode<K, T> vertex1, GraphNode<K, T> vertex2,
			GRAPH_EDGE_DIRECTION edgedirection)
	{
		if (edgedirection == GRAPH_EDGE_DIRECTION.LEFTTORIGHT)
		{
			LinkedList<GraphEntry<GraphNode<K, T>, NM>> linkedList = adjacencyList
					.get(vertex1);
			if (linkedList != null && linkedList.contains(vertex2))
			{
				linkedList.remove(vertex2);
			} else
			{
				throw new MissingDataException();
			}
		} else if (edgedirection == GRAPH_EDGE_DIRECTION.LEFTTORIGHT)
		{
			LinkedList<GraphEntry<GraphNode<K, T>, NM>> linkedList = adjacencyList
					.get(vertex2);
			if (linkedList != null && linkedList.contains(vertex1))
			{
				linkedList.remove(vertex1);
			} else
			{
				throw new MissingDataException();
			}
		} else
		{
			LinkedList<GraphEntry<GraphNode<K, T>, NM>> linkedList = adjacencyList
					.get(vertex1);
			if (linkedList != null && linkedList.contains(vertex2))
			{
				linkedList.remove(vertex2);
			} else
			{
				throw new MissingDataException();
			}
			linkedList = adjacencyList.get(vertex2);
			if (linkedList != null && linkedList.contains(vertex1))
			{
				linkedList.remove(vertex1);
			} else
			{
				throw new MissingDataException();
			}
		}

	}

	
	public void removeVertex(GraphNode<K, T> vertex)
	{
		for (GraphNode<K, T> vertex1 : adjacencyList.keySet())
		{
			LinkedList<GraphEntry<GraphNode<K, T>, NM>> linkedList = adjacencyList
					.get(vertex1);
			if (linkedList != null && !linkedList.contains(vertex))
			{
				linkedList.remove(vertex);
			} else
			{
				throw new MissingDataException();
			}
		}
		adjacencyList.remove(vertex);
	}

	
	public void addEdge(K key1, K key2, NM weight,
			GRAPH_EDGE_DIRECTION direction)
	{
		GraphNode<K, T> vertex1 = new GraphNodeImpl<K, T>(key1, (T) key1);
		GraphNode<K, T> vertex2 = new GraphNodeImpl<K, T>(key2, (T) key2);
		addEdge(vertex1, vertex2, weight, direction);
	}

	
	public void addVertex(K key1)
	{
		GraphNode<K, K> vertex1 = new GraphNodeImpl<K, K>(key1, key1);
		if (!adjacencyList.containsKey(vertex1))
		{
			adjacencyList.put((GraphNode<K, T>) vertex1, null);
		}

	}

	
	public void addVertices(List<K> vertices)
	{
		for (K vertex : vertices)
		{
			addVertex(vertex);
		}
		System.out.println(vertices);
	}

	
	public void addEdge(GraphNode<K, T> vertex1, GraphNode<K, T> vertex2,
			NM weight, GRAPH_EDGE_DIRECTION direction)
	{
		LinkedList<GraphEntry<GraphNode<K, T>, NM>> linkedList = null;
		if (direction == Apply.GRAPH_EDGE_DIRECTION.LEFTTORIGHT)
		{
			linkedList = adjacencyList.get(vertex1);
			if (linkedList == null)
			{
				linkedList = new LinkedList<GraphEntry<GraphNode<K, T>, NM>>();
				adjacencyList.put(vertex1, linkedList);
			}
			if (!linkedList.contains(vertex2))
			{
				GraphEntry<GraphNode<K, T>, NM> entry = new GraphEntry<GraphNode<K, T>, NM>(
						vertex2, weight);
				linkedList.add(entry);
			}

		} else if (direction == Apply.GRAPH_EDGE_DIRECTION.RIGHTTOLEFT)
		{
			linkedList = adjacencyList.get(vertex2);
			if (linkedList == null)
			{
				linkedList = new LinkedList<GraphEntry<GraphNode<K, T>, NM>>();
				adjacencyList.put(vertex2, linkedList);
			}
			if (!linkedList.contains(vertex1))
			{
				GraphEntry<GraphNode<K, T>, NM> entry = new GraphEntry<GraphNode<K, T>, NM>(
						vertex1, weight);
				linkedList.add(entry);
			}
		} else
		{
			linkedList = adjacencyList.get(vertex1);
			if (linkedList == null)
			{
				linkedList = new LinkedList<GraphEntry<GraphNode<K, T>, NM>>();
				adjacencyList.put(vertex1, linkedList);
			}
			if (!linkedList.contains(vertex2))
			{
				GraphEntry<GraphNode<K, T>, NM> entry = new GraphEntry<GraphNode<K, T>, NM>(
						vertex2, weight);
				linkedList.add(entry);
			}
			linkedList = adjacencyList.get(vertex2);
			if (linkedList == null)
			{

				linkedList = new LinkedList<GraphEntry<GraphNode<K, T>, NM>>();
				adjacencyList.put(vertex2, linkedList);
			}
			if (!linkedList.contains(vertex1))
			{
				GraphEntry<GraphNode<K, T>, NM> entry = new GraphEntry<GraphNode<K, T>, NM>(
						vertex1, weight);
				linkedList.add(entry);
			}
		}

	}

	
	public void addEdges(LinkedHashMap<K, K> hashMap,
			LinkedList<NM> weightList, GRAPH_EDGE_DIRECTION direction)
	{
		if (weightList.size() != hashMap.size())
		{
			throw new MissingDataException();
		}
		for (K key : hashMap.keySet())
		{
			addEdge(key, hashMap.get(key), weightList.element(), direction);
		}

	}

	public String toString()
	{
		String returnData = "";
		for (GraphNode<K, T> vertex1 : adjacencyList.keySet())
		{
			if (adjacencyList.get(vertex1) != null)
			{
				for (Map.Entry<GraphNode<K, T>, NM> vertex1ConnectedTo : adjacencyList
						.get(vertex1))
				{
					returnData = returnData + "\n" + vertex1.toString()
							+ "--------->" + vertex1ConnectedTo.toString();
				}
			}
		}
		return returnData;
	}

	public static class GraphEntry<KE, VE extends Number> implements Map.Entry<KE, VE>
	{
		public GraphEntry(KE key, VE value)
		{
			this.key = key;
			if (value == null)
			{
				this.value = (VE) new Integer(0);
			} else
				this.value = value;

		}

		KE key;
		VE value;

		 
		public KE getKey()
		{
			// TODO Auto-generated method stub
			return key;
		}

		 
		public VE getValue()
		{
			// TODO Auto-generated method stub
			return value;
		}

		 
		public VE setValue(VE object)
		{
			// TODO Auto-generated method stub
			return null;
		}

		public String toString()
		{
			return key.toString() + ";Edge Size=" + value.toString();
		}
	}

	 
	public void addEdges(K[] keyArray1, K[] keyArray2, NM[] weightArray,
			GRAPH_EDGE_DIRECTION direction)
	{
		if (weightArray == null || keyArray2 == null || keyArray1 == null
				|| keyArray2.length != weightArray.length
				|| keyArray1.length != keyArray2.length)
		{
			throw new MissingDataException();
		}
		for (int i=0;i<keyArray1.length ;i++)
		{
			addEdge(keyArray1[i], keyArray2[i], weightArray[i], direction);
		}

	}

	 
	public void addVertices(K[] vertices)
	{
		for (K vertex : vertices)
		{
			addVertex(vertex);
		}
		System.out.println(vertices);
		
	}

	 
	public LinkedHashMap<GraphNode<K, T>, LinkedList<GraphEntry<GraphNode<K, T>, NM>>> getAdjacencyList() {
		// TODO Auto-generated method stub
		return adjacencyList;
	}

	 
	public GRAPH_DIRECTION getGraphType() {
		// TODO Auto-generated method stub
		return graphDirection;
	}

}
